#include "xen/xen.hpp"
#include <random>

struct BallPhysicalInfo {
    Xen::Ref<Xen::Physical::SphereBody> body;
    Xen::Ref<Xen::Physical::SphereGeomentry> geom;
};

class SandBoxODE: public Xen::Layer {
public:
    SandBoxODE()
        : Layer("SandBoxODE"),
        dirlight_({{1, 1, 1}, {0, 0, 0}, {0, 0, 0}}, {-1, -1, -1})
    {
        controller_ = std::make_unique<Xen::PerspectiveCameraController>(
                45,
                static_cast<float>(Xen::Application::Get().GetWindow().GetSize().w)/Xen::Application::Get().GetWindow().GetSize().h,
                0.01, 100
                );
        initModel();

        initPhysical();
    }

    void OnUpdate(Xen::TimeStep ts) override {
        Xen::RenderCommand::SetClearColor({0.2, 0.2, 0.2, 1});
        Xen::RenderCommand::Clear();

        Xen::Renderer3D::BeginScene(controller_->GetCamera(), dirlight_, dotlight_, spotlight_);
        {
            for (auto& info : infos_) {
                glm::vec3 ball_position = info.body->GetPosition();
                glm::vec3 ball_rotation = info.body->GetRotation();
                Xen::Renderer3D::DrawModel(ball_model_, ball_position, ball_rotation);
            }
            Xen::Renderer3D::DrawModel(brick_wall_, {0, -5, -10}, {0, 0, 0}, {20, 1, 20});
        }
        Xen::Renderer3D::EndScene();

        controller_->OnUpdate(ts);

        stepPhysical(ts);
    }

    void OnImGuiRender() override {

    }

    void OnEvent(Xen::Event &event) override {
        controller_->OnEvent(event);
        if (Xen::Input::IsKeyPressed(Xen::XEN_SPACE)) {
            if (!is_space_pressing) {
                addBall();
                is_space_pressing = true;
            } else {
                is_space_pressing = false;
            }
        }

    }

    ~SandBoxODE() {
    }

private:
    std::unique_ptr<Xen::PerspectiveCameraController> controller_;
    Xen::Texture2DLibrary library_;
    Xen::DirectLight dirlight_;
    Xen::DotLight dotlight_;
    Xen::SpotLight spotlight_;
    Xen::Ref<Xen::Model> ball_model_;
    Xen::Ref<Xen::Model> brick_wall_;

    bool is_space_pressing = false;

    void initModel() {
        ball_model_ = Xen::ModelLoader::Create(library_, "sandbox/assets/textures/Basketball/Bball.dae");
        brick_wall_ = Xen::ModelLoader::Create(library_, "sandbox/assets/textures/brick/brick.obj");
    }

    Xen::Physical::World world_;
    Xen::Physical::Space space_;
    std::list<BallPhysicalInfo> infos_;
    Xen::Ref<Xen::Physical::PlaneGeomentry> plane_geom1_;
    dJointGroupID joint_group_;

    void initPhysical() {
        world_.SetGravity(glm::vec3(0, -9, 0));

        plane_geom1_ = space_.CreatePlane({0, 1, 0, -5});
        joint_group_ = dJointGroupCreate(0);
    }

    void addBall() {
        std::random_device dev;
        std::uniform_real_distribution<float> dist(-2, 2);
        auto body = world_.CreateSphere(10, glm::vec3(dist(dev), 5, -10), 0.8);
        auto geom = space_.CreateShpere(0.8);
        body->AttachGeom(geom);
        infos_.push_back({body, geom});
    }

    struct Data {
        dWorldID world;
        dJointGroupID joint_group;
    };

    static void nearCallback(void *data, dGeomID o1, dGeomID o2) {
        const int N = 10;
        dContact contact[N];
        int n = dCollide(o1, o2, N, &contact[0].geom, sizeof(contact));
        for (int i = 0; i < n; i++) {
            contact[i].surface.mode = dContactBounce;
            contact[i].surface.mu = dInfinity;
            contact[i].surface.bounce = 0.8;
            contact[i].surface.bounce_vel = 0;
            Data* d = (Data*)data;
            dJointID joint = dJointCreateContact(d->world, d->joint_group, &contact[i]);
            dJointAttach(joint, dGeomGetBody(contact[i].geom.g1), dGeomGetBody(contact[i].geom.g2));
        }
    }

    void stepPhysical(Xen::TimeStep ts) {
        Data data = {world_.GetRaw(), joint_group_};
        space_.Update(SandBoxODE::nearCallback, (void*)&data);
        world_.Update(ts);
        dJointGroupEmpty(joint_group_);
    }

};
